﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Media.Imaging;

using System.Text;
using System.Threading;
using System.Collections;
using System.Runtime.InteropServices;

using Algorithms;

namespace GameEngine
{
    /// <summary>
    ///     
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e">The new position.</param>
    public delegate void PositionChangedHandler(object sender, PositionChangedArgs e);

    /// <summary>
    ///     Arguments for the PositionChange Event.
    /// </summary>
    public class PositionChangedArgs : EventArgs
    {
        private IntPoint _newPositionTile;
        private IntPoint _newPosition;
        public PositionChangedArgs(IntPoint newPositionTile, IntPoint newPosition)
        {
            this._newPositionTile = newPositionTile;
            this._newPosition = newPosition;
        }
        public IntPoint NewPositionTile
        {
            get
            {
                return _newPositionTile;
            }
        }
        public IntPoint NewPosition
        {
            get
            {
                return _newPosition;
            }
        }
    }

    public class AnimatedGameObject :GameObject
    {

        /// <summary>
        ///     Velocity of this object.
        /// </summary>
        public IntPoint Velocity { get; set; }
        /// <summary>
        ///     Pathfinder used for ground movement.
        /// </summary>
        public PathFinder PathFinderGround { get; set; }
        /// <summary>
        ///     Pathfinder used for aerial movement.
        /// </summary>
        public PathFinder PathFinderAir { get; set; }
        /// <summary>
        ///     Path returned by pathfinding.
        /// </summary>
        protected List<PathFinderNode> _path = new List<PathFinderNode>();
        /// <summary>
        ///     Destination tile to be reached when moving.
        /// </summary>
        //protected IntPoint _destination;
        /// <summary>
        ///     True if animations has been set, false otherwise. Used in Animate().
        /// </summary>
        protected bool _animationSet = false;
        /// <summary>
        ///     Animation used for all directions.
        /// </summary>
        protected Spritesheet _movementAnimation;


        /// <summary>
        ///     Fires when the object changes position.
        /// </summary>
        public event PositionChangedHandler PositionChanged;
        protected virtual void OnPositionChanged(PositionChangedArgs e)
        {
            if (this.PositionChanged!=null)
                PositionChanged(this, e);
        }


        /// <summary>
        ///     Horizontal directon of the movement.
        /// </summary>
        protected enum HorizontalDirection
        {
            none,
            left,
            right
        }

        /// <summary>
        ///     Vertical directon of the movement.
        /// </summary>
        protected enum VerticalDirection
        {
            none,
            up,
            down
        }


        /// <summary>
        ///     Vertical direction of the current movement-used for animation.
        /// </summary>
        protected VerticalDirection _verDir;
        /// <summary>
        ///     Horizontal direction of the current movement-used for animation.
        /// </summary>
        protected HorizontalDirection _horDir;
        /// <summary>
        ///     Used for movement.
        /// </summary>
        protected double _elapsedSecondsMovement = 0;
        /// <summary>
        ///     Used for animation.
        /// </summary>
        protected double _elapsedSecondsAnimation = 0;
        /// <summary>
        ///     Change in postion.
        /// </summary>
        protected IntPoint _positionChange = new IntPoint(0, 0);
        /// <summary>
        ///     
        /// </summary>
        protected double _frameLength;
        public double FrameLength
        {
            get
            {
                return this._frameLength;
            }
            set
            {
                this._frameLength = value;
            }
        }

        /// <summary>
        ///     True if the object flies, false if it moves on ground. Used for pathfinding.
        /// </summary>
        protected bool _flying = false;
        /// <summary>
        ///     True if the object flies, false if it moves on ground. Used for pathfinding.
        /// </summary>
        public bool Flying
        {
            get
            {
                return this._flying;
            }
            set
            {
                this._flying = value;
            }
        }
        
        /// <summary>
        ///     Offset of the base of the AnimatedObject from its upper-left corner
        ///     (for characters it means how far away are their feet from the upper-left 
        ///     corner of the image).
        /// </summary>
        protected IntPoint _baseOffset;
        /// <summary>
        ///     Offset of the base of the AnimatedObject from its upper-left corner
        ///     (for characters it means how far away are their feet from the upper-left 
        ///     corner of the image).
        /// </summary>
        public IntPoint BaseOffset
        {
            get 
            {
                return this._baseOffset;
            }
            set
            {
                this._baseOffset = value;
            }
        }



        /// <summary>
        ///     Animates this object based on the time elapsed.
        /// </summary>
        /// <param name="elapsedTime">Time elapsed from previous render.</param>
        public virtual void Animate(TimeSpan elapsedTime)
        {
            //to be implemented
        }


        /// <summary>
        ///     Sets the animation to be used for this object.
        /// </summary>
        /// <param name="movementAnimation">Animation.</param>
        /// <param name="frameLength">
        /// How long(for how many seconds) the animation does not change frame.
        /// (e.g. 4 - frame changes after every 4 seconds).
        /// </param>
        public virtual void SetAnimation(Spritesheet movementAnimation, double frameLength)
        {
            //this._movementAnimation = new Spritesheet(movementAnimation);
            this._movementAnimation = Spritesheet.Clone(movementAnimation);
            this._animationSet = true;
            this._frameLength = frameLength;
        }


        /// <summary>
        ///     Moves the object on the gamegrid along the path.
        /// </summary>
        public virtual void Move(TimeSpan elapsedTime, IntSize tileSize)
        {
            //defaultly the object is not moving
            this._horDir = HorizontalDirection.none;
            this._verDir = VerticalDirection.none;

            //if the path exists and is not of 0 length
            if (this._path != null && this._path.Count > 1)
            {
                //only moves the object when it moves at least 1 whole pixel, so we don't have to use fractions of pixels
                _elapsedSecondsMovement += elapsedTime.TotalSeconds;
                
                //can't be higher than 1- because it can cause weird jumps
                //if (_elapsedSecondsMovement > 1.0)
                    //_elapsedSecondsMovement = 1.0;

                if ((_elapsedSecondsMovement * this.Velocity.X >= 1.0) && (_elapsedSecondsMovement * this.Velocity.Y >= 1.0))
                {
                    this._positionChange = new IntPoint(0, 0);
                    IntPoint targetPosition = new IntPoint((this._path[1].X * tileSize.Width) - this._baseOffset.X, (this._path[1].Y * tileSize.Height) - this._baseOffset.Y);
                   
                    //fixes if the offset sets the target position outside of the map
                    //we dont fix this because the head of the character can be off map
                    /*if (targetPosition.X < 0)
                        targetPosition.X = 0;
                    if (targetPosition.Y < 0)
                        targetPosition.Y = 0;*/

                    //note:replace 0 offset with base position-base for ball is 0
                    //base position for character would be different

                    //finds the horizontal direction and moves the object based on its velocity
                    if (this.Position.X > targetPosition.X)
                    {
                        this._horDir = HorizontalDirection.left;
                        this._positionChange.X = -(int)((double)this.Velocity.X * _elapsedSecondsMovement);
                    }
                    else if (this.Position.X < targetPosition.X)
                    {
                        this._horDir = HorizontalDirection.right;
                        this._positionChange.X = (int)((double)this.Velocity.X * _elapsedSecondsMovement);
                    }
                    else
                    {
                        this._horDir = HorizontalDirection.none;
                    }

                    //finds the vertical direction and moves the object based on its velocity
                    if (this.Position.Y > targetPosition.Y)
                    {
                        this._verDir = VerticalDirection.up;
                        this._positionChange.Y = -(int)((double)this.Velocity.Y * _elapsedSecondsMovement);
                    }
                    else if (this.Position.Y < targetPosition.Y)
                    {
                        this._verDir = VerticalDirection.down;
                        this._positionChange.Y = (int)((double)this.Velocity.Y * _elapsedSecondsMovement);
                    }
                    else
                    {
                        this._verDir = VerticalDirection.none;
                    }

                    //we don't jump over target position
                    //if (Math.Pow(this._positionChange.X, 2) > (Math.Pow((this.Position.X - targetPosition.X), 2)))
                    if (Math.Abs(this._positionChange.X) > (Math.Abs(this.Position.X - targetPosition.X)))
                    {
                        if (this._horDir == HorizontalDirection.left)
                            this._positionChange.X = targetPosition.X - this.Position.X;
                        else
                            this._positionChange.X = targetPosition.X - this.Position.X;
                    }
                    //we don't jump over target position
                    //if (Math.Pow(this._positionChange.Y, 2) > (Math.Pow((this.Position.Y - targetPosition.Y), 2)))
                    if (Math.Abs(this._positionChange.Y) > (Math.Abs(this.Position.Y - targetPosition.Y)))
                    {
                        if (this._verDir == VerticalDirection.up)
                            this._positionChange.Y = targetPosition.Y - this.Position.Y;
                        else
                            this._positionChange.Y = targetPosition.Y - this.Position.Y;
                    }

                    this.Position.X += this._positionChange.X;
                    this.Position.Y += this._positionChange.Y;

                    //we don't go off map
                    //we dont fix this because the head of the character can be off map
                    /*if (Position.X < 0)
                        Position.X = 0;
                    if (Position.Y < 0)
                        Position.Y = 0;*/

                    //when we reach the target position we remove this position from the list
                    if (this.Position.X == targetPosition.X && this.Position.Y == targetPosition.Y)
                    {
                        this._path.RemoveAt(1);
                    }

                    //calculates the tile positions
                    this.TilePosition.X = this.Position.X / tileSize.Width;
                    this.TilePosition.Y = this.Position.Y / tileSize.Height;

                    //zeroes the time elapsed
                    this._elapsedSecondsMovement = 0;
                    //fires the PositionChanged event
                    OnPositionChanged(new PositionChangedArgs(this.TilePosition, this.Position));
                }
            }
        }
    }
}
